#include "BinanceAccount.h"
#include <iostream>

BinanceAccount* BinanceAccount::account = nullptr;

BinanceAccount* BinanceAccount::getInstance(const std::string& api_key,const std::string& secret_key){
    if (account == nullptr) {
        account = new BinanceAccount(api_key,secret_key);
    }
    return account;
}

void BinanceAccount::connect(){
    getListenKey();
    std::thread keep_alive(&BinanceAccount::keep_user_stream, this);
    while (true) {
        websocketpp::lib::error_code ec;
        client::connection_ptr con = m_account_client.get_connection("wss://stream.binance.com/ws/" + userStreamKey, ec);
        if (ec) {
            std::cout << "Could not create account connection because: " << ec.message() << std::endl;
            return;
        }
        m_account_client.connect(con);
        std::thread account_ws(&BinanceAccount::run_account_client, this);
        account_ws.join();
        m_account_client.reset();
    }
    keep_alive.join();
}

double BinanceAccount::getBalance(const std::string& coin){
    if(coinBalance.count(coin) > 0){
        return coinBalance[coin];
    }
    return 0.0;
}

void BinanceAccount::initBalance(const std::map<std::string,double>& balances){
    for(const auto& pair : balances){
        coinBalance[pair.first] = pair.second;
    }
}

void BinanceAccount::keep_user_stream(){
    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(1800));
        putListenKey();
    }
}

void BinanceAccount::run_account_client(){
    m_account_client.run();
}

void BinanceAccount::on_account_message(websocketpp::connection_hdl hdl, message_ptr msg){
    nlohmann::json json_data = nlohmann::json::parse(std::string(msg->get_payload()));
    if(json_data["e"] == "outboundAccountPosition"){
        for(const auto& json_b:json_data["B"]){
            std::string coin = json_b["a"];
            coinBalance[coin] = std::stod(std::string(json_b["f"]).c_str());
        }
        std::cout<<"[Account Update]:"<< json_data << std::endl;
        //logi("[Account Update]: {}",std::string(msg->get_payload()));
    }
}

void BinanceAccount::on_account_open(websocketpp::connection_hdl hdl){
    std::cout << "Connected to Trade WebSocket" << std::endl;
}

void BinanceAccount::on_account_close(websocketpp::connection_hdl hdl){
    std::cout << "Disconnected from Trade WebSocket" << std::endl;
}

std::string BinanceAccount::getSinnature(const std::string& data){
    unsigned char* result;
    static char res_hexstring[65];
    result = HMAC(EVP_sha256(), secretKey.c_str(), secretKey.length(), (unsigned char*)data.c_str(), data.length(), NULL, NULL);
    for (int i = 0; i < 32; i++) {
        sprintf(&(res_hexstring[i * 2]), "%02x", result[i]);
    }
    return std::string(res_hexstring, 64);
}

size_t BinanceAccount::WriteCallback(void* contents, size_t size, size_t nmemb, void* userp){
    ((std::string*)userp)->append((char*)contents, size * nmemb);
    return size * nmemb;
}

void BinanceAccount::getListenKey(){
    CURL* curl;
    CURLcode res;
    std::string readBuffer;
    curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.binance.com/api/v3/userDataStream");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    } else {
        try {
            nlohmann::json jsonData = nlohmann::json::parse(readBuffer);
            if (jsonData.contains("listenKey")) {
                userStreamKey = jsonData["listenKey"];
            }
        } catch (const nlohmann::json::parse_error& e) {
            std::cerr << "JSON parsing error: " << e.what() << std::endl;
        }
    }
    //logi("get:{}",userStreamKey);
}

void BinanceAccount::putListenKey(){
    CURL* curl;
    CURLcode res;
    std::string readBuffer;
    curl = curl_easy_init();
    if (!curl) {
        std::cerr << "Failed to initialize CURL" << std::endl;
        return;
    }
    struct curl_slist* headers = NULL;
    headers = curl_slist_append(headers, ("X-MBX-APIKEY: " + apiKey).c_str());
    curl_easy_setopt(curl, CURLOPT_URL, ("https://api.binance.com/api/v3/userDataStream?listenKey="+userStreamKey).c_str());
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    res = curl_easy_perform(curl);
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    if (res != CURLE_OK) {
        std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
    } else {
        try {
            nlohmann::json jsonData = nlohmann::json::parse(readBuffer);
            if (jsonData.contains("listenKey")) {
                userStreamKey = jsonData["listenKey"];
            }
        } catch (const nlohmann::json::parse_error& e) {
            std::cerr << "JSON parsing error: " << e.what() << std::endl;
        }
    }
    //logi("put:{}",userStreamKey);
}

BinanceAccount::BinanceAccount(const std::string& api_key,const std::string& secret_key):apiKey(api_key),secretKey(secret_key){
    m_account_client.clear_access_channels(websocketpp::log::alevel::all);
    m_account_client.clear_error_channels(websocketpp::log::elevel::all);
    m_account_client.init_asio();
    m_account_client.set_tls_init_handler([](websocketpp::connection_hdl) {
        return websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tlsv12);
    });
    m_account_client.set_message_handler(std::bind(&BinanceAccount::on_account_message, this, std::placeholders::_1, std::placeholders::_2));
    m_account_client.set_open_handler(std::bind(&BinanceAccount::on_account_open, this, std::placeholders::_1));
    m_account_client.set_close_handler(std::bind(&BinanceAccount::on_account_close, this, std::placeholders::_1));
}
